package com.zmn.mcc.cas.server.controller.cas;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import com.zmn.brs.common.enums.EventEnum;
import com.zmn.brs.common.enums.EventEnum;
import com.zmn.brs.common.enums.RecordFieldEnum;
import com.zmn.brs.starter.annotation.RecordField;
import com.zmn.brs.starter.annotation.StaffOperateRecord;
import com.zmn.common.constant.GlobalConsts;
import com.zmn.common.constant.StatusConsts;
import com.zmn.common.dictionary.KvModel;
import com.zmn.common.dto2.ResponseDTO;
import com.zmn.common.utils.captcha.GifCaptchaUtil;
import com.zmn.common.utils.cookie.CookieUtil;
import com.zmn.common.utils.crypt.AESUtils;
import com.zmn.common.utils.date.DateUtil;
import com.zmn.common.utils.http.HttpUtil;
import com.zmn.common.utils.number.NumberUtil;
import com.zmn.common.utils.request.RequestUtil;
import com.zmn.common.utils.string.StringUtil;
import com.zmn.common.utils.useragent.UserAgentDO;
import com.zmn.common.utils.useragent.UserAgentUtil;
import com.zmn.mcc.business.interfaces.staff.ip.StaffIpInfoBService;
import com.zmn.mcc.cache.StaffCache;
import com.zmn.mcc.cas.core.ShiroTokenManager;
import com.zmn.mcc.cas.core.utils.CasUtils;
import com.zmn.mcc.cas.model.LoginStaff;
import com.zmn.mcc.cas.server.CasServerConsts;
import com.zmn.mcc.cas.server.CasServerStaffService;
import com.zmn.mcc.cas.server.StaffLoginService;
import com.zmn.mcc.cas.server.model.LoginDTO;
import com.zmn.mcc.cas.server.model.StaffLoginDIO;
import com.zmn.mcc.cas.server.model.StaffPasswordDIO;
import com.zmn.mcc.cas.web.StaffAdminService;
import com.zmn.mcc.common.constant.DingTalkConsts;
import com.zmn.mcc.common.constant.LoginTypeConsts;
import com.zmn.mcc.common.constant.StaffChangeLogConsts;
import com.zmn.mcc.common.constant.StaffLogConsts;
import com.zmn.mcc.model.entity.log.IpInfo;
import com.zmn.mcc.model.entity.staff.McStaff;
import com.zmn.mcc.model.entity.staff.securitylog.StaffSecurityLog;
import com.zmn.mcc.model.entity.staff.staffchangelog.StaffChangeLog;
import com.zmn.mcc.services.interfaces.staff.securitylog.StaffSecurityLogService;
import com.zmn.mcc.services.interfaces.staff.staffchangelog.StaffChangeLogService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import com.zmn.mcc.cas.core.MccCookie;

import javax.annotation.Resource;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
 * @author nowind
 * @since 16/1/3 21:04
 * @modifier tanbiao 登录添加白名单限制
 */
@Controller
@RequestMapping("/cas")
@Slf4j
public class CasModifyController {
    private Logger logger = LoggerFactory.getLogger(CasModifyController.class);

    /**
     * cookie 加密key
     */
    public final static String COOKIE_USER_KEY = "1839cc7f0770ff67cffe057f4b4e1540";

    @Resource
    private StaffLoginService staffLoginService;

    @Resource
    private ShiroTokenManager shiroTokenManager;

    @Resource
    private StaffChangeLogService staffChangeLogService;

    @Resource
    StaffIpInfoBService staffIpInfoBService;


    private String userInfoKey = COOKIE_USER_KEY;

    /**
     * 功能描述：未设置白名单默认值
     *
     * @author tanbiao
     * @since 2019/9/18
     * @return
     * @modifier
     */
    private final int DEFAULT_WHITELIST = 1;

    /**
     * 功能描述：非初始密码标志位
     *
     * @author tanbiao
     * @since 2019/9/18
     * @return
     * @modifier
     */
    private final int STAFF_INIT_PASSWORD = 1;

    /**
     * 登录用户信息
     */
    @Value("${user.info.cookie.user:zmn_user}")
    String cookieUser;

    @Value("${user.info.cookie.domain:.xiujiadian.com}")
    private String userInfoDomain;

    @Value("${user.info.timeout:604800}")
    private int userInfoTimeout;

    /**
     * 管理员信息接口
     */
//    @Value("${zmn.admin.info}")
//    private String adminInfoUrl;

//    @Value("${zmn.admin.expire:600000}")
//    private long adminInfoExpire;
//
//    @Resource
//    RedisManager redisManager;

//    @Resource
//    private CaptchaService captchaService;

//    @Resource
//    SysService sysService;

//    @Resource
//    private WhitelistService whitelistService;

    @Resource
    private StaffSecurityLogService staffSecurityLogService;

    @Resource
    private CasServerStaffService casServerStaffService;

//    @Resource
//    private RedisAtomicFactory redisAtomicFactory;

    protected static final String TAG_BLACK = "IP白名单";

    // 主框架页面域名
    @Value("${cas.server.loginSuccessUrl:index.action}")
    private String loginSuccessUrl;

    // 主框架页面域名
    @NacosValue(value="${cas.server.bindStaffUrl:bind.action}", autoRefreshed = true)
    private String bindStaffUrl;

    @NacosValue(value="${cas.server.cubeUrl:index.action}", autoRefreshed = true)
    private String cubeUrl;

    /**
     * 登录
     *
     * @param request
     * @param response
     * @param staffLogin
     * @return
     * @modifier tanbiao 20190918 新增IP白名单限制
     */
    @StaffOperateRecord(staffId = "{{#staffLogin.operatorId}}",
            event = EventEnum.ADMIN_LOGIN,
            mapping = {
                @RecordField(field = RecordFieldEnum.STAFF_IP, value = "{{#staffLogin.operatorIP}}")
    })
    @RequestMapping(value = "login")
    @ResponseBody
    public ResponseDTO login(HttpServletRequest request, HttpServletResponse response, @RequestBody StaffLoginDIO staffLogin) {

        String staffName = staffLogin.getStaffName();
        String qrCodeUrl = staffLogin.getQrCodeUrl();

        if (StringUtils.isEmpty(staffName) && StringUtils.isEmpty(qrCodeUrl)) {
            return new LoginDTO(StatusConsts.STATUS_ERROR, staffLogin.getRedirectUrl(), "ERROR", "");
        }

        if (StringUtils.isNotEmpty(staffName)) {
            return loginByAccount(request, response, staffLogin);
        } else {
            return loginByQrCode(request, response, staffLogin);
        }


    }

    /**
     * 添加所有业务系统到Session
     */
    private void addSysToSession() {
        Session session = shiroTokenManager.getSession();
        List<KvModel<String, String>> list = CasUtils.convertSysName2UrlKvModeLList(casServerStaffService.listApp());
        for (KvModel<String, String> each : list) {
            session.setAttribute(each.getKey(), each);
        }
    }

    /**
     * 退出登录
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "logout")
    public ModelAndView loginout(HttpServletRequest request, HttpServletResponse response) {
        logout(request, response);
        return directToLogin();
    }

    /**
     * 临时密码登录
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "tmpLogin")
    @ResponseBody
    public ResponseDTO tmpLogin(HttpServletRequest request, HttpServletResponse response,  @RequestBody StaffLoginDIO staffLogin) {

        Integer logId = staffLogin.getLogId();
        if (NumberUtil.isNotNullOrZero(logId) && StaffCache.SPECIAL_STAFF_MAP.get(logId) != null) {
            if (!Objects.equals(StaffCache.SPECIAL_STAFF_MAP.get(logId), staffLogin.getStaffName())) {
                return new LoginDTO(StatusConsts.STATUS_ERROR, staffLogin.getRedirectUrl(), "ERROR", "专属链接无此权限！");
            }
        }

        //先退出
        logout(request, response);
        //登录
        return loginByAccount(request, response, staffLogin);
    }

    /**
     * 工作台跳转免登
     *
     * @param request
     * @param response
     * @return
     */
    @RequestMapping(value = "authCodeLogin")
    @ResponseBody
    public ResponseDTO authCodeLogin(HttpServletRequest request, HttpServletResponse response, @RequestBody StaffLoginDIO staffLogin) {
        String dingTalkUserId = staffLoginService.getDingTalkUserId(staffLogin.getAuthCode());
        if (null == dingTalkUserId) {
            return new LoginDTO(StatusConsts.STATUS_ERROR, "", "ERROR", "钉钉免登失败，" +
                    "未获取到钉钉用户id");
        }
        staffLogin.setDTalkUserId(dingTalkUserId);
        McStaff staff = casServerStaffService.queryStaffByDTalkId(dingTalkUserId);
        //如果为空则要求绑定账号密码
        if (Objects.isNull(staff)) {
            String bindUrl = bindStaffUrl + "?redirectUrl=" + ""
                    + "&dTalkUserId=" + dingTalkUserId;
            return new LoginDTO(StatusConsts.STATUS_SUCCESS, bindUrl, "OK", "登录成功！");
        }
        staffLogin.setOperatorId(staff.getStaffId());
        staffLogin.setOperatorIP(HttpUtil.getIPAddress(request));
        staffLogin.setStaffName(staff.getStaffName());
        staffLogin.setPassword(staff.getPassword());
        //跳转魔方系统
        if (staffLogin.jumpToCube()) {
            staffLogin.setRedirectUrl(cubeUrl);
        }
        ResponseDTO responseDTO = loginByAccount(request, response, staffLogin);
        logger.info("登录结果 responseDTO= {}", responseDTO.toString());
        return responseDTO;
    }


    /**
     * 修改密码
     *
     * @param dio
     * @return
     */
    @RequestMapping(value = "updatePwd", method = RequestMethod.POST)
    @ResponseBody
    public ResponseDTO updatePwd(HttpServletRequest request, HttpServletResponse response, @RequestBody StaffPasswordDIO dio) throws UnsupportedEncodingException {
        String pwd = dio.getPassword();
        if (pwd == null) {
            return ResponseDTO.fail(CasServerConsts.LOGIN_PWD_NULL_MSG);
        }
        if (GlobalConsts.ZMN_STAFF_DEFAULT_PASSWORD.equals(pwd)) {
            return ResponseDTO.fail("不能将密码修改为初始密码");
        }
        LoginStaff staff = StaffAdminService.currentStaff();
        try {
            casServerStaffService.updatePassword(staff.getStaffId(), dio.getPassword(), staff.getRealName());

            // 变动记录
            String ip = RequestUtil.getRequestIp(request);
            IpInfo ipInfo = staffIpInfoBService.locationByIP(ip);
            String abutmenterCity = null;
            if (Objects.nonNull(ipInfo)) {
                abutmenterCity = ipInfo.getProvince() + "-" + ipInfo.getCity();
            }

            StaffChangeLog staffChangeLog = StaffChangeLog.builder()
                    .staffId(staff.getStaffId())
                    .beforContent("-")
                    .afterContent("修改成功")
                    .abutmenterId(StaffAdminService.currentStaff().getStaffId())
                    .abutmenter(StaffAdminService.currentStaff().getRealName())
                    .abutmenterDeptId(StaffAdminService.currentStaff().getDeptId() + "")
                    .abutmenterDeptName(StaffAdminService.currentStaff().getDeptName())
                    .changeType(StaffChangeLogConsts.CHANGE_TYPE_MODIFY_PASS)
                    .createTime(DateUtil.getNow())
                    .ip(ip)
                    .abutmenterCity(abutmenterCity)
                    .build();
            staffChangeLogService.insert(staffChangeLog);

            // 写入登出日志
            StaffSecurityLog log = new StaffSecurityLog();
            log.setStaffId(staff.getStaffId());
            log.setCreater(staff.getRealName());
            log.setLogType(StaffLogConsts.SECURITY_LOG_UPDATE_PWD);
            log.setOpertorIp(RequestUtil.getRequestIp(request));
            log.setCreateTime(new Date());
            staffSecurityLogService.insert(log);
        } catch (Exception ex) {
            logger.error("#cas#updatePwd 修改密码失败，user:{}", dio, ex);
            return ResponseDTO.fail("修改密码失败！");
        }
        return ResponseDTO.success();
    }


    private ResponseDTO loginByQrCode(HttpServletRequest request, HttpServletResponse response, @RequestBody StaffLoginDIO staffLogin) {
        String result = HttpUtil.get(staffLogin.getQrCodeUrl());
        logger.info("result = {}", result);
        ResponseDTO dio = JSONObject.parseObject(result, ResponseDTO.class);
        if (!dio.isSuccess()) {
            if (null != dio.getMessage() && dio.getMessage().contains("找不到该用户")) {
                dio.setMessage("非内部账号，禁止登录");
            }
            return new LoginDTO(StatusConsts.STATUS_ERROR, staffLogin.getRedirectUrl(), "OK", dio.getMessage());
        }

        String dingTalkUserId = (String) dio.getData();
        if (StringUtils.isEmpty(dingTalkUserId)) {
            return new LoginDTO(StatusConsts.STATUS_ERROR, staffLogin.getRedirectUrl(), "OK", "获取钉钉用户id失败，请重新扫码");
        }
        McStaff staff = casServerStaffService.queryStaffByDTalkId(dingTalkUserId);
        //如果为空则要求绑定账号密码
        if (Objects.isNull(staff)) {
            String bindUrl = bindStaffUrl + "?redirectUrl=" + staffLogin.getRedirectUrl()
                    + "&dTalkUserId=" + dingTalkUserId;
            return new LoginDTO(StatusConsts.STATUS_SUCCESS, bindUrl, "OK", "登录成功！");
        }
        staffLogin.setOperatorId(staff.getStaffId());
        staffLogin.setOperatorIP(HttpUtil.getIPAddress(request));
        staffLogin.setStaffName(staff.getStaffName());
        staffLogin.setPassword(staff.getPassword());
        staffLogin.setDTalkUserId(dingTalkUserId);
        ResponseDTO responseDTO = loginByAccount(request, response, staffLogin);
        return responseDTO;
    }

    /**
     * 跳转登录页或管理中心首页
     *
     * @param message
     * @return
     */
    protected ModelAndView directToLoginMessage(String message, String redirectUrl) {
        ModelAndView model = directToLogin();
        model.addObject("message", message);
        model.addObject("redirectUrl", redirectUrl);
        return model;
    }

    protected ModelAndView directToLogin() {
        return directToLogin("");
    }

    protected ModelAndView directToLogin(String param) {
        String url;
        ModelAndView mav;
        if (staffLoginService.isEnableAccountLogin()) {
            url = String.format("cas/login");
        } else {
            url = String.format("cas/qrcode_login");
        }
        if (StringUtil.isNotBlank(param)) {
            url += ("?" + param);
        }
        mav = new ModelAndView(url);
        if (!staffLoginService.isEnableAccountLogin()) {
            mav.addObject("appId", DingTalkConsts.DD_APP_ID);
            mav.addObject("appSecret", DingTalkConsts.DD_APP_SECRET);
        }
        return mav;
    }

    private ResponseDTO loginByAccount(HttpServletRequest request, HttpServletResponse response, @RequestBody StaffLoginDIO staffLogin) {
        String staffName = staffLogin.getStaffName();
        String password = staffLogin.getPassword();
        String redirectUrl = StringUtils.isEmpty(staffLogin.getRedirectUrl()) ? loginSuccessUrl : staffLogin.getRedirectUrl();

        if (StringUtils.isNotEmpty(staffLogin.getRedirectUrl()) && cubeUrl.equals(staffLogin.getRedirectUrl())) {
            redirectUrl = cubeUrl;
        }

        if (StringUtil.isBlank(staffName)) {
            return new LoginDTO(StatusConsts.STATUS_ERROR, redirectUrl, "ERROR", "请输入用户名！");
        }
        if (StringUtil.isBlank(password)) {
            return new LoginDTO(StatusConsts.STATUS_ERROR, redirectUrl, "ERROR", "请输入密码！");
        }

        String requestIp = RequestUtil.getRequestIp(request);

        response.setHeader("pragma", "no-cache");
        response.setHeader("cache-control", "no-cache");
        response.setIntHeader("expires", GlobalConsts.NONE);

        try {

            if (SecurityUtils.getSubject().isAuthenticated()) {
                return new LoginDTO(StatusConsts.STATUS_SUCCESS, StringUtils.defaultIfBlank(redirectUrl, loginSuccessUrl), "OK", "登录成功！");
//                return new LoginDTO(StatusConsts.STATUS_ERROR, "ERROR", redirectUrl, "您已登录，请退出后再重新登录");
            }

            UserAgentDO agent = UserAgentUtil.getUserAgent(request);
            String deviceType = String.format("%s %s", agent.getOsName(), agent.getOsVersion());
            if (StringUtils.isNotEmpty(deviceType)) {
                staffLogin.setDeviceType(deviceType);
            }

            // 记录用户设备等信息
            Session session = shiroTokenManager.getSession();
            if (session != null) {
                session.setAttribute("ip", requestIp);
                session.setAttribute("deviceType", deviceType);
            }

            Cookie cookie = CookieUtil.getCookie(request, GifCaptchaUtil.CAPTCHE_COOKIE_KEY);

            //根据登录方式选择对应的验证方法
            Integer loginType;
            if (StringUtils.isNotEmpty(staffLogin.getDTalkUserId())) {
                loginType = LoginTypeConsts.LOGIN_TYPE_QRCODE;
            } else {
                loginType = LoginTypeConsts.LOGIN_TYPE_ACCOUNT;

            }
            LoginDTO dto = staffLoginService.login(staffLogin, requestIp, (cookie != null ? cookie.getValue() : null), loginType);

            if (!dto.isSuccess()) {
                return dto;
            }


            // 添加用户信息到cookie，浏览器退出则过期
            LoginStaff user = StaffAdminService.currentStaff();
            String info = user.getStaffId() + "|" + user.getCompanyId() + "|" + user.getRealName() + "|" + user.getPlat() + "|" + System.currentTimeMillis();
            String encryptInfo = Hex.encodeHexString(AESUtils.encrypt(info.getBytes(GlobalConsts.UTF8_ENCODING), userInfoKey));
            MccCookie infoCookie = new MccCookie();
            infoCookie.setName(cookieUser);
            infoCookie.setDomain(userInfoDomain);
            infoCookie.setMaxAge(userInfoTimeout);
            infoCookie.setValue(encryptInfo);

            // 如果是https，就添加secure属性和允许跨域标识（http添加这俩属性,会导致cookie被浏览器拒绝）
            StringBuffer requestURL = request.getRequestURL();
            try {
                URL url = new URL(requestURL.toString());
                String protocol = url.getProtocol();
                if (protocol.equalsIgnoreCase("https")) {
                    infoCookie.setSecure(Boolean.TRUE);
                    infoCookie.setSameSite("None");
                }
            } catch (MalformedURLException e) {
                log.error(e.getMessage());
            }

            infoCookie.saveTo(request, response);

            logger.info("#cas#login success; staffName:{}, ip:{}", staffLogin.getStaffName(), requestIp);

            addSysToSession();  // 待废弃代码

//            String argSplit = "?";
//            String argAnd = "&";
            if (dto.isSuccess()) {
//                if (!redirectUrl.contains(argSplit)) {
//                    redirectUrl += argSplit;
//                } else {
//                    redirectUrl += argAnd;
//                }
//                redirectUrl += "ticket=" + shiroTokenManager.getSession().getId() + "&timestamp=" + System.currentTimeMillis(); // 暂时保留此行
                return new LoginDTO(StatusConsts.STATUS_SUCCESS, StringUtils.defaultIfBlank(redirectUrl, loginSuccessUrl), "OK", "登录成功！");
            }

        } catch (Exception ex) {
            logger.error("#cas#login 登录失败,user:{}", staffLogin, ex);
            String message = ex.getMessage();
            if (StringUtils.isEmpty(message)) {
                message = "登录失败";
            } else if (message.length() > 200) {
                message = "登录失败：" + message.substring(0, 200);
            } else {
                message = "登录失败：" + message;
            }
            return new LoginDTO(StatusConsts.STATUS_ERROR, "ERROR", redirectUrl, message);
        }
        return new LoginDTO(StatusConsts.STATUS_SUCCESS, loginSuccessUrl, "OK", "登录成功！");
    }

    /**
     * 退出操作
     * @param request
     * @param response
     */
    private void logout(HttpServletRequest request, HttpServletResponse response) {
        try {
            String requestIp = RequestUtil.getRequestIp(request);
            staffLoginService.logout(requestIp);
            //移除
            SimpleCookie infoCookie = new SimpleCookie();
            infoCookie.setName(cookieUser);
            infoCookie.setDomain(userInfoDomain);
            infoCookie.removeFrom(request, response);
        } catch (Exception ex) {
            logger.error(String.format("[zmn]WEB后台退出登录失败,staffName:[%s],ex:[%s]", shiroTokenManager.getUserTokenKey(), ex.getMessage()));
        }
    }

}
